簡單介紹什麼是 custom Hook。
目前為止都是切分畫面把畫面上的內容拆成小元件做處理,在 react 裡面可以透過創建自己的 custom hook 來讓邏輯共用,比如 input 的 handler 或是 fetch api 等。
use
開頭 ( 要小寫開頭跟其他 react hooks 一樣 )。假設我有兩個 input 要管理,可能會使用兩個 useState 跟 handle function 作處理。
function App() {
const [firstName, setFirstName] = useState<string>("Evan");
const [lastName, setLastName] = useState<string>("Hsu");
function handleFirstNameChange({
target,
}: React.ChangeEvent<HTMLInputElement>) {
setFirstName(target.value);
}
function handleLastNameChange({
target,
}: React.ChangeEvent<HTMLInputElement>) {
setLastName(target.value);
}
return (
<div>
<label>
First name:
<input value={firstName} onChange={handleFirstNameChange} />
</label>
<br />
<label>
Last name:
<input value={lastName} onChange={handleLastNameChange} />
</label>
<p>
<b>
Good morning, {firstName} {lastName}.
</b>
</p>
</div>
);
}
可以看到元件裡面有兩個重複的 useState 跟 handler function,所以我們可以嘗試把重複的地方拆出來,製作管理 input 的 custom hook。
function useInput(initValue: string) {
const [value, setValue] = useState(initValue);
function handleChange({ target }: React.ChangeEvent<HTMLInputElement>) {
setValue(target.value);
}
return {
value: value,
onChange: handleChange,
};
}
這樣我們就把管理 input 的 change 跟 state 封裝在這一個 useInput
裡面了,要使用的時候就可以這樣用。
function App() {
const firstNameProps = useInput("Evan");
const lastNameProps = useInput("Hsu");
return (
<div>
<label>
First name:
<input {...firstNameProps} />
</label>
<br />
<label>
Last name:
<input {...lastNameProps} />
</label>
<p>
<b>
Good morning, {firstNameProps.value} {lastNameProps.value}.
</b>
</p>
</div>
);
}
簡化過後的 App 元件裡面也變得更加簡潔,好閱讀跟管理。
除了 Input 以外常常被封裝成 custom hook 的還有 fetch 的行為。
function useFetchUrl(url: string) {
const [data, setData] = useState([]);
const [status, setStatus] = useState("");
useEffect(() => {
setStatus("Loading");
let ignore = false;
setData([]);
fetch(url)
.then((result) => result.json())
.then((data) => {
if (!ignore) {
setStatus("Success");
setData(data);
}
})
.catch(() => {
setStatus("Error");
});
return () => {
ignore = true;
};
}, [url]);
return { data, status };
}
這個 useFetchUrl 的 custom hook 除了回傳 fetch 取得的資料以外,也會回傳當前 fetch 的狀態,在元件使用的時候可以依照不同的狀態做不同的處理,如下。
function App() {
const { data, status } = useFetchUrl("https://api.....");
return (
<div>
{status === "Loading" && <p>Loading....</p>}
{status === "Error" && <p>Something went wrong....</p>}
{status === "Success" && data.map((item) => <p>{item}</p>)}
</div>
);
}
這樣就可以在不同的地方共用這個 useFetchUrl 的 custom hook,讓元件裡面的程式碼更簡潔。
還有另外一個常見的 custom hook,就是 useClickOutside,在瀏覽網頁時,常常會跳出提示或互動用的彈窗,通常可以透過點擊彈窗外的範圍把彈窗關閉,這也可以寫成一個 custom hook。
const useClickOutSide = (callback: () => void) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClick = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback();
}
};
document.addEventListener("click", handleClick, true);
return () => document.removeEventListener("click", handleClick, true);
}, [ref, callback]);
return ref;
};
這個 custom hook 接收一個 function 當點擊指定 DOM 節點外面的時候要進行什麼行為,然後會 return 一個 ref,讓使用這個 hook 的元件可以把 ref 放在希望作用的 DOM 節點上。
function Dialog({ close }: { close: () => void }) {
const ref = useClickOutSide(close);
return (
<div className="dialog">
<div ref={ref}>
<p>Hello!!!!</p>
<button onClick={close}>Close</button>
</div>
</div>
);
}
這邊我把 ref 放在了 Dialog 的 DOM 節點上,而 close function 則是從外面傳進來的 關閉 dialog 的 function。
function App() {
const [open, setOpen] = useState(false);
const handleOpenDialog = () => setOpen(true);
const handleCloseDialog = () => setOpen(false);
return (
<div>
<button onClick={handleOpenDialog}>Open</button>
{open && <Dialog close={handleCloseDialog} />}
</div>
);
}
實際看起來就會是這樣。
當我點擊 close 按鈕或是 dialog 的外面時元件就會被關閉。
Reusing Logic with Custom Hooks - react document
下一篇簡單介紹如何透過 Render Props 的方法讓父元件決定子元件要顯示的內容
如果內容有誤再麻煩大家指教,我會盡快修改。
這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium